/******************************************************************************
This file is part of AppKit.
Project: appkit
Author : FergusZeng
Email  : cblock@126.com
git	   : https://gitee.com/newgolo/appkit.git
*******************************************************************************
MIT License

Copyright (c) 2022 cblock@126.com

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
******************************************************************************/
#pragma once

#include <sys/epoll.h>

#include <atomic>
#include <iostream>
#include <memory>
#include <mutex>
#include <string>
#include <vector>

#include "appkit/basetype.h"
/**
 *  @file   fileio.h
 *  @brief  文件工具类
 */
namespace appkit {

/** 定义输入输出模式 */
enum IO_MODE_E {
    IO_MODE_RD_ONLY = 0,    /**< 只可读(r) */
    IO_MODE_WR_ONLY,        /**< 只可写 */
    IO_MODE_RDWR_ONLY,      /**< 只可读写(r+) */
    IO_MODE_APPEND_ONLY,    /**< 只增加 */
    IO_MODE_REWR_ORNEW,     /**< 文件重写,没有则创建(w) */
    IO_MODE_RDWR_ORNEW,     /**< 文件可读写,没有则创建(w+) */
    IO_MODE_APPEND_ORNEW,   /**< 文件可增加,没有则创建(a) */
    IO_MODE_RDAPPEND_ORNEW, /**< 文件可读或可增加,没有则创建(a+) */
    IO_MODE_INVALID = 0xFF, /**< 非法模式 */
};

/**
 *  @class  IODevice
 *  @brief  IO设备抽象类
 */
class IODevice {
    DECL_CLASSMETA(IODevice)
    using Ptr = std::shared_ptr<IODevice>;

public:
    IODevice();
    virtual ~IODevice();
    /**
     *  @brief  打开设备
     *  @param  devName 设备全名(包含路径,如:/dev/ttyS0)
     *  @param  mode 打开模式IO_MODE_E
     *  @return 设备打开成功返回true,失败返回false
     *  @note   none
     */
    virtual bool open(const std::string& devName, int ioMode = IO_MODE_INVALID);
    /**
     *  @brief  关闭设备
     *  @param  void
     *  @return 设备关闭成功返回true,失败返回false
     *  @note   none
     */
    virtual bool close();
    /**
     * @brief 读数据
     * @param buf
     * @param len
     * @return int
     */
    virtual int readData(char* buf, int len);
    /**
     * @brief 写入数据
     * @param buf
     * @param len
     * @return int
     */
    virtual int writeData(const char* buf, int len);
    /**
     * @brief 接收数据
     * @param buf
     * @param len
     * @param usTimeout
     * @return int
     */
    virtual int recvData(char* buf, int len, int usTimeout = -1);
    /**
     * @brief 发送数据
     * @param buf
     * @param len
     * @param usTimeout
     * @return int
     */
    virtual int sendData(const char* buf, int len, int usTimeout = -1);
    /**
     * @brief 设置属性
     * @param attr
     * @param value
     * @return int
     */
    virtual int setAttribute(int attr, int value);
    /**
     * @brief 获取属性值
     * @param attr
     * @return int
     */
    virtual int getAttribute(int attr);
    /**
     * @brief 获取设备描述符
     * @return int
     */
    virtual int fd();
    /**
     * @brief 判断设备是否打开
     * @return true
     * @return false
     */
    virtual bool isOpen();

    /**
     * @brief 重新打开设备
     * @return true
     * @return false
     */
    virtual bool reopen();

    /**
     * @brief 强制写入缓冲区数据到文件中
     *
     */
    virtual void flush();

protected:
    int m_fd{-1};
    int m_openMode{-1};
    std::string m_name{""};
};

/**
 * @class File
 * @brief 文件类
 */
class File : public IODevice {
    DECL_CLASSMETA(File)

public:
    enum FileType { REG = 0, DIR, CHR, BLK, LINK, SOCK, FIFO };

public:
    File();
    virtual ~File();
    /**
     * @brief 判断文件是否存在
     * @param fileName 文件名
     * @return true 文件存在
     * @return false 文件不存在(如果fileName是目录也返回false)
     */
    static bool exists(const std::string& fileName);

    /**
     * @brief 获取文件类型
     * @param fileName
     * @return int
     */
    static int type(const std::string& fileName);
    /**
     * @brief 获取文件大小
     * @param fileName
     * @return int
     */
    static int getSize(const std::string& fileName);
    /**
     * @brief 删除文件
     * @param fileName
     * @return true
     * @return false
     */
    static bool removeFile(const std::string& fileName);

    /**
     * @brief 重命名文件
     * @param oldFile
     * @param newFile
     * @return true
     * @return false
     */
    static bool renameFile(const std::string& oldFile,
                           const std::string& newFile);

    /**
     * @brief 截取文件
     * @param fileName
     * @param size
     * @return bool
     */
    static bool truncFile(const std::string& fileName, int size);

    /**
     * @brief 读取文件
     * @param fileName 文件名
     * @return std::string 文件内容
     */
    static std::string readAll(const std::string& fileName);

    /**
     * @brief 判断文件是否打开
     * @return true
     * @return false
     */
    bool isOpen();

    /**
     * @brief 打开文件(继承自IODevice)
     * @param fileName 文件名
     * @param ioMode IO_MODE_E
     * @return true
     * @return false
     */
    bool open(const std::string& fileName, int ioMode) override;
    /**
     * @brief 关闭文件(继承自IODevice)
     * @return true
     * @return false
     */
    bool close() override;
    /**
     * @brief 读取文件(继承自IODevice)
     * @param buf
     * @param len
     * @return int 读取数据的长度,读取失败返回-1
     */
    int readData(char* buf, int len) override;
    /**
     * @brief 写入文件(继承自IODevice)
     * @param buf
     * @param len
     * @return int 写入数据的长度,写入失败返回-1
     */
    int writeData(const char* buf, int len) override;

    /**
     * @brief 强制写入缓冲区数据到文件中
     */
    void flush() override;

    /**
     * @brief 将文件映射到内存
     * @return void* 内存地址
     */
    void* mapMemory();
    /**
     * @brief 取消内存映射
     * @return true
     * @return false
     */
    bool unmapMemory();
    /**
     * @brief 读取一行
     * @param lineStr
     * @return int 行长度(不包括换行符),-1表示出错或读完.
     */
    int readLine(std::string* lineStr);

    /**
     * @brief 读取所有内容
     * @return std::string
     */
    std::string readAll();

    /**
     * @brief 读取指定长度字符串
     * @param size 0表示读取所有
     * @return std::string
     */
    std::string readString(size_t size = 0);
    /**
     * @brief 获取文件大小
     * @return int
     */
    int getSize();
    /**
     * @brief 获取文件名称
     * @return std::string
     */
    std::string getName();
    /**
     * @brief 是否到文件结尾
     * @return true
     * @return false
     */
    bool isEnd();
    /**
     * @brief 获取当前读写位置
     * @return int
     */
    int getPos();
    /**
     * @brief 设置当前读写位置
     * @param pos
     * @return int
     */
    int setPos(int pos);

private:
    FILE* m_fp{nullptr};
    void* m_mmaddr{nullptr};
};

/**
 * @class FilePath
 * @brief 文件路径
 */
class FilePath {
    DECL_CLASSMETA(FilePath)

public:
    explicit FilePath(const std::string& filePath);
    ~FilePath();
    /**
     * @brief 增加子路径
     * @param path 子路径
     * @return FilePath&
     */
    FilePath& addPath(const std::string& path);
    const std::string path() { return m_filePath; }
    /**
     * @brief 获取文件路径中的文件夹部分
     * @return std::string
     */
    std::string dirName();
    /**
     * @brief 获取文件路径中的文件名部分
     * @return std::string
     */
    std::string baseName();
    /**
     * @brief 获取文件名后缀
     * @return std::string
     * @note 如文件名为~/Document/note.txt,调用该方法将返回".txt"
     */
    std::string suffix();
    /**
     * @brief 创建文件链接
     * @param filePath 文件路径
     * @param linkPath 链接路径
     * @param hard 是否是硬链接
     * @return true 创建成功
     * @return false 创建失败
     */
    static bool createLink(const std::string& filePath,
                           const std::string& linkPath, bool hard = false);

private:
    std::string m_filePath{""};
};

/**
 *  @class  Directory
 *  @brief  目录类.
 */
class Directory {
    DECL_CLASSMETA(Directory)

public:
    Directory();
    ~Directory();
    /**
     * @brief 判断目录是否为空
     * @param dirName
     * @return true 目录为空或不存在
     * @return false 目录不为空
     */
    static bool isEmpty(const std::string& dirName);
    /**
     * @brief 判断目录是否存在
     * @param dirName
     * @return true 目录存在
     * @return false 目录不存在或dirName不是目录
     */
    static bool exists(const std::string& dirName);
    /**
     * @brief 获取目录大小(包含子目录及所有文件，包括硬链接文件)
     * @param dirName
     * @return uint32
     */
    static uint32 getSize(const std::string& dirName);
    /**
     * @brief 创建目录
     * @param dirName
     * @param recursive
     * @return true
     * @return false
     */
    static bool createDir(const std::string& dirName, int mode,
                          bool recursive = false);
    /**
     * @brief 删除目录
     * @param dirName
     * @param recursive
     * @return true
     * @return false
     */
    static bool removeDir(const std::string& dirName, bool recursive = false);
    /**
     * @brief 获取目录下的文件列表(包含目录)
     * @param dirName 目录名称
     * @param regexp 正则表达式(用于匹配文件名)
     * @return std::vector<std::string>
     * @note : 常用文件名匹配正则表达式
     *  匹配字母数字开头,字母数字结束的字符串:
     *  "^([a-zA-Z0-9]+).*([a-zA-Z0-9]+)$"
     *  匹配带年月日的日志文件:
     *  "^.*_[0-9]{8}-[0-9]{6}.log"
     */
    static std::vector<std::string> getFileList(const std::string& dirName,
                                                const std::string& regexp);
    /**
     * @brief 进入目录
     * @param dirName
     * @return true
     * @return false
     */
    bool enterDir(const std::string& dirName);
    /**
     * @brief 获取当前目录路径
     * @return std::string
     */
    std::string currentDir();
    /**
     * @brief 列出当前目录下的所有文件(包含目录)
     * @return std::vector<std::string>
     */
    std::vector<std::string> getFileList();

private:
    std::string m_currentDir{"."};
};

/**
 * @brief 磁盘分区
 * @class Partition
 */
class Partition {
    DECL_CLASSMETA(Partition)
public:
    bool statist(const std::string& volume);
    size_t totalBytes() { return m_total; }
    size_t freeBytes() { return m_free; }
    size_t usedBytes() { return m_used; }

private:
    size_t m_total;
    size_t m_free;
    size_t m_used;
};

/**
 * @brief 轮询事件
 * @class PollEvent
 */
class PollEvent {
    DECL_CLASSMETA(PollEvent)
public:
    enum EVENT_E {
        POLLIN = 0,  // 读
        POLLOUT,     // 写
    };

public:
    PollEvent() {}
    PollEvent(int fd, int event) : m_fd(fd), m_event(event) {}
    int fd() const { return m_fd; }
    int event() const { return m_event; }

private:
    int m_fd{-1};
    int m_event{-1};
};

/**
 * @class Poller
 * @brief IO复用/轮询集
 */
class Poller {
    DECL_CLASSMETA(Poller)

public:
    Poller();
    virtual ~Poller();
    /**
     * @brief 打开复用集
     * @param onceNotify 只通知一次
     * @return true
     * @return false
     */
    bool open(int maxEvents, bool onceNotify = false);
    /**
     * @brief 关闭复用集
     * @param void
     */
    void close();
    /**
     * @brief 增加轮询事件
     * @param event
     * @return true
     * @return false
     */
    bool addEvent(const PollEvent& event);
    /**
     * @brief 移除轮询事件
     * @param event
     * @return true
     * @return false
     */
    bool removeEvent(const PollEvent& event);
    /**
     * @brief 等待事件
     * @param usTimeout
     * @return 事件集
     */
    std::vector<std::shared_ptr<PollEvent>> waitEvent(int usTimeout);

private:
    int m_epfd{-1};
    int m_size{0};
    bool m_onceNotify{false};
    int m_eventNum{0};
    struct epoll_event* m_events{nullptr};
    std::mutex m_mutex;
};

/**
 * @class PollDevice
 * @brief 轮询设备
 * @note 用于抽象支持PollEvent的设备,便于Poller进行统一管理
 */
class PollDevice {
public:
    explicit PollDevice(IODevice::Ptr dev) : m_dev(dev) {}
    virtual ~PollDevice() { m_dev = nullptr; }
    virtual PollEvent pollEvent() = 0;
    virtual bool onPoll() = 0;

private:
    IODevice::Ptr m_dev{nullptr};
};
}  // namespace appkit
